home *** CD-ROM | disk | FTP | other *** search
Text File | 1990-06-24 | 123.2 KB | 2,584 lines | [TEXT/CCL ] |
- ; (c) Copyright 1990 by University of Massachusetts. All rights reserved.
- ; This software was conceived, designed, and written by Dan Suthers
- ; while supported by the National Science Foundation under grant number
- ; MDR 8751362, and by a fellowship from Apple Computer, Inc., Cupertino,
- ; CA. Partial support was also received from the Office of Naval Research
- ; under a University Research Initiative Grant, contract N00014-86-K-0764.
- ; Mr. Suthers created this software under his own initiative while in an
- ; academic relationship with the University of Massachusetts. The above
- ; copyright notice was a condition placed by University lawyers on approval
- ; of distribution of this software by Apple Computer, and is not meant to
- ; imply that this software was created in an employment or "work for hire"
- ; relationship between the University and Mr. Suthers.
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ; File: Grapher.lisp
- ; Author: Dan Suthers
- ; Created: 25-Sep-88 09:58:14
- ; Modified: 24-Jun-90 23:03:21 (Dan Suthers)
- ; Language: LISP
- ; Package: GRAPHER
- ;
- ; Description: Draws directed graphs on the Macintosh in Allegro Common Lisp.
- ; Supports a variety of graph node and layout styles. Nodes are
- ; mouse sensitive, and mouse actions can be defined. A generic
- ; grapher for SM objects is provided, which also serves as an
- ; example of its use.
- ;
- ; (c) Copyright 1988, by Daniel D. Suthers
- ; Department of Computer and Information Science
- ; University of Massachusetts
- ; Amherst, Massachusetts 01003
- ;
- ; This software was conceived, designed, and written by Dan Suthers
- ; while supported by the National Science Foundation under grant number
- ; MDR 8751362, and by a fellowship from Apple Computer, Inc., Cupertino,
- ; CA. Partial support was also received from the Office of Naval Research
- ; under a University Research Initiative Grant, contract N00014-86-K-0764.
- ; I wish to acknowledge the generous support of Beverly Woolf, who obtained
- ; the above grants and encouraged me to pursue my own research interests in
- ; her lab. This work would not have been possible without the resources and
- ; stimulating environment of the Computer and Information Science department.
- ;
- ; Permission to use, modify, and distribute this software is granted subject
- ; to the following restrictions and understandings:
- ; 1. The file header, including this notice, shall be retained, and may be
- ; extended to include documentation of modifications to the software.
- ; 2. This material is for nonprofit educational and research purposes only.
- ; Users are requested, but not required, to inform Mr. Suthers of any
- ; noteworthy uses of this software.
- ; 3. Mr. Suthers and the University of Massachusetts make no warrantee or
- ; representation that the operation of this software will be error free,
- ; and are under no obligation to provide any services.
- ; 4. Any user of such software agrees to indemnify and hold harmless Mr.
- ; Suthers and the University of Massachusetts from all claims arising
- ; out of the use or misuse of this software, or arising out of any
- ; accident, injury, or damage whatsoever, and from all costs, counsel
- ; fees, and liabilities incurred in or about any such claim, action, or
- ; proceeding brought thereon.
- ; 5. All materials and reports developed as a consequence of the use of
- ; this software shall duly acknowledge such use, in accordance with
- ; the usual standards of acknowledging credit in academic research.
- ;
- ; Status: Stable and quite usable, though the programmer's interface is
- ; rather low level, and the code is ugly in places.
- ;
- ; Changes:
- ; 24-Jul-88 NIL now represents the null graph view; added clear-graph-view.
- ; 08-Aug-88 Adding mouse sensitivity and associated operations.
- ; 04-Sep-88 Nodes now moved by holding mouse down.
- ; 16-Sep-88 Windows now have grow boxes (usually hidden by clear).
- ; 22-Sep-88 Better Menu, more control over SM graphs, eliminated
- ; clear-graph-window, backlinks attach at better points.
- ; 24-Sep-88 Generalizing mouse methods: the slot is now an alist of
- ; symbolic option names to (lambda (view node) ...) methods.
- ; 22-Oct-88 Cleaned up SM grapher; using oval and round-rect for node
- ; status. Added mouse method for updating when SM instances change.
- ; 01-Nov-88 A graph-view which is about to be destroyed is not redrawn
- ; when the mouse-methods menu dissappears, exposing the graph window.
- ; This keeps the user from having to wait for superfluous redraws.
- ; 04-Nov-88 Added :frame and :round-frame node styles for multiple labels.
- ; Backup to Parent View mouse method restores gv to gw if there is no
- ; parent view (bug introduced 01-Nov which set gv to nil).
- ; 06-Nov-88 Fixed longstanding bug that vertical alignment was off in
- ; :vertical-tree style ... box-size was not being updated in a loop.
- ; 08-Nov-88 Efficiency improvements. "Magic Numbers" declared as constants.
- ; 09-Dec-88 Multiple-menu choice on recompute layout.
- ; 16-Dec-88 Mouse methods now compiled (but SAVE-GRAPH-VIEW can't save them).
- ; 31-Dec-88 WINDOW-DRAW-CONTENTS retains selected-node, and highlights
- ; it correctly, eliminating problem with redraw after popup dialogues.
- ; Eliminated GRAPH-NODE-PARENTS. Fixed "NIL is an invalid point" problem
- ; when recomputing layout of radials. (The layout was being changed
- ; while the window was still redrawing where the grapher menu was. Put
- ; in time delay on all menu items changing layout to let this complete.)
- ; 15-Jan-89 GRAPH-SM-OBJECTS and SM-TYPE->GRAPH-VIEW now have optional
- ; argument which lets you specify how to compute graph node labels.
- ; 22-Sep-89 Added require of SMEDIT, since EDITS used.
- ; 25-Oct-89 Windows now automatically layout & redraw graph when resized.
- ; 26-Oct-89 Mouse action menus now have first item selected as default.
- ; 30-Oct-89 Inspect Structure added to SM Mouse Methods. GRAPH-SM-OBJECTS
- ; menu item now checks sm:type-info for :graph-view-roots and for
- ; :graph-view-child-slot or :graph-view-parent-slot before asking.
- ; 01-Nov-89 Added :none-frame graph node box style.
- ; 08-Nov-89 Added default mouse method to show graph view parameters.
- ; 21-Dec-89 Mouse-method popups now come in upper left of the window.
- ; Exported two macros to help others do this: UPPER-LEFT-POPUP-POSITION
- ; and WITH-UPPER-LEFT-POPUP. Relies on change made to DIALOGE.lisp today.
- ; Fixed error when graph-view-parameter-dialog has null table sequences.
- ; 02-Jan-90 Fixed run-mouse-method to accept double clicks in menu.
- ; Added * versions of layout style, which put all childless nodes in
- ; the first layer before starting the tree in the next layer. Outlines
- ; now drawn every half second when moving nodes; was too slow.
- ; 11-Jan-90 Mouse menu now shows 7 items for faster selection, and is
- ; labeled with object rather than graph node if the object is available.
- ; 23-Jan-90 Graph view parameters now in document dialog.
- ; 29-Jan-90 Updated for version 1.3.1. WINDOW-DRAW-CONTENTS -> VIEW-...;
- ; :default-button specified in button item.
- ; 12-Feb-90 Zoom box added to graph windows.
- ; 15-Mar-90 Added interpretation of arrow keys to move up and down in
- ; graphs that have mouse methods for this. Very bogus implementation,
- ; this whole thing needs rewriting in object oriented style!
- ; 26-Apr-90 DS Fixed bug in layout where all level-1 nodes were pushed
- ; down deeper, leaving the first level empty. Now it checks for this
- ; and shifts all the nodes up one level if needed.
- ; Attachment points are now computed nicely depending on relative
- ; position of nodes rather than relative layering.
- ; Also, *STARRED-STYLES* and *FRAMED-STYLES* parameterized.
- ; 08-May-90 DS Finally figured out an easy way to reduce link crossings;
- ; added an ordering style which does this.
- ; 24-Jun-90 DS WITH-UPPER-LEFT-POPUP macro not being expanded when mouse
- ; methods compiled -- too lazy to figure out why; replaced with LET.
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- #|
-
- Guide to the Perplexed:
-
- We have found this grapher to be extremely useful in a variety of
- applications, including graphing concept hierarchies; frames linked by
- the slots between them; traces of rule-based reasoners; "DACTNs", which
- are graphical representations of discourse generation procedures; and
- in graphing various data structures generated by Artificial Intelligence
- programs we are writing in the areas of Explanation and Tutoring Systems.
-
- However, you are warned that
- * It is coded in a mixture of CCL objects for windows and SM structures
- for graph "views" and "nodes", and is not as clean as a purely object
- oriented program. (In short, the code is wierd.)
- * The programmer's interface is low level, and it may take a bit of
- effort to understand it at first. You must first understand the SM
- package for managing Common Lisp structures.
- * I have not had time to write a user's manual, other than the following.
-
- This grapher differs from most in that it tries to fit the entire graph
- into the window. (Most graphers layout the graph in as much virtual space
- as is needed, and then use a scrollable window which displays only part of
- the graph if the graph is too large to fit.) My justification is that this
- grapher was intended to be used with applications which are "intelligent"
- enough to select digestable quantities of information, rather than
- overwhelming the user with a huge graph. Navigation through the graph
- is done by selecting commands on dialog menus which move from one meaningful
- "view" to another, rather than by simulating physical movement of a large
- sheet of paper under a window.
-
- Please take the time to read the documentation in the SM.lisp file.
-
- Graphs are displayed in windows of type *graph-window*. Each *graph-window*
- has an object variable GRAPH-VIEW. This is bound to either NIL for an
- empty window, or to the name of an SM structure instance of type GRAPH-VIEW.
- *Graph-window*s know how to translate graph-view structures into the displayed
- graph. Internally, graphs consist of GRAPH-NODEs, which include labels and
- links to other graph-nodes. However, as mentioned above, not all of a given
- graph is displayed at a given time. A graph-view specifies what part of the
- graph should be drawn. As my SM documentation puts it:
-
- A Graph-View is a view (way of drawing graphically) a portion of a graph
- (collection of Graph-Nodes). A given Graph-Node may participate in multiple
- Graph-Views, and a given graph may have multiple views on it. The Graph-View
- is specified in terms of the Graph-Nodes which are the Roots to search the
- graph from, the Depth-Bound of this search, the Style of laying out the found
- graph, and the Ordering used to layout children of a given node.
-
- A Graph-Node is how you specify the label, children, and box style of a vertex of
- the graph to be drawn. It also contains computed, graph-independent information
- concerning the size of the box which is needed to draw the node, and where edge
- attachments may be made to it.
-
- See the SM documentation of this structure in this file for further information
- on graph-view Style, Ordering, etc., and on the box styles for graph-nodes.
- Generally, you can have styles of horizontal and vertical tree or radial layout;
- ordering can be as found, reversed, or reordered to attempt to reduce link
- crossings; and nodes can be ovals, rectangles, rounded rectangles, no box at
- all, or frames, which are multi-line versions of rectangles.
-
- What all this means for you is in order to use the grapher, you have to write
- code that:
- - generates a collection of graph-nodes, including labels and links.
- - creates a graph-view structure specifying how the graph is displayed
- - creates an instance of *graph-window* with the graph-view object variable
- bound to the graph-view structure.
- If you want to have special commands which operate on the graph when a node
- is mouse-selected, you also need to give the *graph-window* a command list
- such as *sm-mouse-methods*, in this file.
-
- This brings me to GRAPH-SM-OBJECTS and associated code. Please see the
- section of this file which includes that function. It exemplifies how I
- use this grapher.
-
- For a quick look at the grapher, load the demo file Grapher-Demo.lisp.
- Play with the mouse, try the menu options, notice how the links change
- position when you move nodes around, and change the window size to get
- a feel for its behavior.
-
- -- Dan Suthers
-
- |#
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (in-package :GRAPHER)
-
- (require :SM)
- (require :SMEDIT) ; edits
- (require :DIALOGUE)
- (require :CONTROL) ; random-choice
- (require :MAPPINGS) ; image
- (require :MATH) ; radians
- (require :MISC) ; unique-symbol, etc.
-
- (require :quickdraw "ccl;LIBRARY:QuickDraw")
- (use-package :CCL)
-
- (export '(
- *graph-window*
- graph-sm-objects
- graph-view-parameter-dialogue
- sm-type->graph-view
- windows-using-graph-view
-
- ;; Window functions
- layout-graph-view
- move-node
- set-graph-view
-
- create-graph-view
- dispose-graph-view
- graph-view
- graph-view-roots
- graph-view-style
- graph-view-ordering
- graph-view-depth-bound
- graph-view-node-font
- graph-view-text-font
- graph-view-border-width
- graph-view-mouse-methods
- graph-view-info
- graph-view-info-image
- save-graph-view
-
- create-graph-node
- graph-node
- graph-node-object
- graph-node-box-style
- graph-node-children
- graph-node-connector
- graph-node-label
-
- upper-left-popup-position
- with-upper-left-popup
-
- ))
-
- ;;; Best default for Allegro CL -- see manual's implementation notes.
- ;;; - The safety 1 space 2 speed 2 setting lets the compiler trust all
- ;;; type declarations, and eliminates event-processing in iterative loops.
- ;;; - We crank this up to safety 1 space 2 speed 3 for heavy computation,
- ;;; so fixnum operations are guaranteed to return fixnums, and car and
- ;;; cdr don't check types (but an error would crash Allegro).
- ;;; - A drop to safety 0 would eliminate number of argument and stack
- ;;; overflow checks, skip some event processing, and make slot access
- ;;; open coded with no type checking. Timing tests show moderate gains.
-
- (proclaim '(optimize (safety 1) (space 2) (speed 2)))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; MAGIC NUMBERS AND OTHER PARAMETERS
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; This does not contain all parameters -- just those which occur in
- ;;; disjoint places, and thus would be hard to update, and some which
- ;;; occur localized but are likely to be modified.
-
- (defconstant *STARRED-STYLES* '(:horizontal-tree* :vertical-tree* :radial*)
- "Graph view styles which put childess nodes alone in the first layer.")
-
- (defconstant *FRAMED-STYLES* '(:frame :round-frame :none-frame)
- "Graph node styles which can have multiple lines.")
-
- ;;; Points to indent from edge of screen to windows.
- (defconstant *SCREEN-EDGE-INDENTATION* 2)
-
- ;;; Font size must be smaller than this to be called "small".
- (defconstant *SMALL-FONT-THRESHOLD* 10)
-
- ;;; How much to pad the box-height beyond the actual height in points of the font.
- ;;; More padding is needed for ovals.
- (defconstant *SMALL-FONT-BOX-HEIGHT-PADDING* 2)
- (defconstant *LARGE-FONT-BOX-HEIGHT-PADDING* 4)
- (defconstant *SMALL-FONT-OVAL-BOX-HEIGHT-PADDING* 6)
- (defconstant *LARGE-FONT-OVAL-BOX-HEIGHT-PADDING* 8)
-
- ;;; How much to pad the box-width beyond the width of the text in points to
- ;;; ensure adequate space between the box and the text. A function of box
- ;;; shape since some shapes "slope in", requiring more padding to clear text.
- (defconstant *RECT-BOX-WIDTH-PADDING* 5)
- (defconstant *ROUND-RECT-BOX-WIDTH-PADDING* 13)
- (defconstant *ROUND-FRAME-BOX-WIDTH-PADDING* 23)
- (defconstant *OVAL-BOX-WIDTH-PADDING* 25)
-
- ;;; How much to indent the text from the box's origin in the H and V dimensions.
- ;;; H indentation a function of box shape for reasons indicated above.
- (defconstant *RECT-TEXT-H-INDENTATION* 3)
- (defconstant *ROUND-RECT-TEXT-H-INDENTATION* 7)
- (defconstant *ROUND-FRAME-TEXT-H-INDENTATION* 12)
- (defconstant *OVAL-TEXT-H-INDENTATION* 13)
- (defconstant *SMALL-FONT-V-TEXT-INDENTATION* 4)
- (defconstant *LARGE-FONT-V-TEXT-INDENTATION* 5)
-
- ;;; How different the H (or V) coordinates of two boxes have to be apart before
- ;;; one is considered to be to the left or right (above or below) the other,
- ;;; rather than in the same position in that dimension. Affects which side the
- ;;; links are connected on.
- (defconstant *RELATIVE-H-POSITION-THRESHOLD* 50) ; roughly average node width?
- (defconstant *RELATIVE-V-POSITION-THRESHOLD* 15) ; roughly average node height.
-
- ;;; Radius of the circular blob drawn to mark the connection point of a link.
- (defconstant *CONNECTOR-RADIUS* 3)
-
- ;;; How far away you can click from a selected node and still count as a double
- ;;; click. Should be more lenient than usual Mac 4 pixels, since graph nodes
- ;;; have larger extent.
- (defconstant *GRAPH-WINDOW-DOUBLE-CLICK-SPACING* 20)
-
- ;;; How long before a mouse held down is counted as such, to ensure user means it.
- (defconstant *GRAPH-WINDOW-MOUSE-DOWN-DELAY* 0.3)
-
- (defconstant *OUTLINE-TIME-INTERVAL*
- (truncate (/ internal-time-units-per-second 2)))
-
-
- ;;; Special Handling of mouse methods on *mouse-methods-replacing-graph-view*
- ;;; prevents the re-drawing of the current graph view by view-draw-contents
- ;;; when the menu dialogue by which the method is selected dissappears (exposing
- ;;; part of the window). This saves the user from having to wait for redraw of
- ;;; complicated graphs which are about to be replaced anyway.
-
- (defparameter *MOUSE-METHODS-REPLACING-GRAPH-VIEW*
- '("Make this Node the Root"
- "Backup Once to Parent View"
- "Update Graph View for Changes"
- "Backup All the Way to Original View"))
-
- ;;; Added to a window's position to get where mouse-generated popups should appear.
- (defconstant *POPUP-OFFSET* (make-point 10 10))
-
- ;;; Things to make it easy to get window-relative upper left popups.
-
- (defmacro UPPER-LEFT-POPUP-POSITION (w)
- `(ask ,w (add-points (window-position) *popup-offset*)))
-
- (defmacro WITH-UPPER-LEFT-POPUP (w &rest body)
- `(let ((wind:*dialogue-position*
- (ask ,w (add-points (window-position) *popup-offset*))))
- ,@body))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; DATA STRUCTURES
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- ;;; Vertices. A set of these implicitly define a DAG by virtue of their
- ;;; children pointers.
-
- (sm:dst (GRAPH-NODE
- (:reusable t)
- (:comments "
- A Graph-Node is how you specify the label, children, and box style of a vertex of
- the graph to be drawn. It also contains computed, graph-independent information
- concerning the size of the box which is needed to draw the node, and where edge
- attachments may be made to it."))
-
- (LABEL ""
- :type (or string list)
- :comments "
- The string labeling the node. Make it short! If BOX-STYLE is :frame,
- :round-frame, or :none-frame this must be a list of strings, which will
- be displayed on separate lines in a multi-line graph node.")
-
- (CHILDREN nil
- :type list
- :comments "
- A list of Graph-Node names of the children of the present node.")
-
- (BOX-STYLE :rect
- :type (member :none :rect :round-rect :oval
- :none-frame :frame :round-frame)
- :comments "
- Indicates what sort of box to draw around the label, if any, and whether there
- are multiple labels (see comments for LABEL).")
-
- (CONNECTOR T
- :type (member t nil)
- :comments "
- Indicates whether to draw a round 'connector' where the arc meets the node box.")
-
- (OBJECT nil
- :comments "
- The application may store here arbitrary information, presumably a pointer to
- the application object which this graph node corresponds to.")
-
- (BOX-SIZE 0
- :type fixnum
- :computed t
- :comments "
- A Macintosh Point indicating the size of the box drawn around the label.")
-
- (TOP-CENTER 0
- :type fixnum
- :computed t
- :comments "
- A Macintosh Point indicating the offset from the position of the node box to
- the center of the Top edge of the box.")
-
- (BOTTOM-CENTER 0
- :type fixnum
- :computed t
- :comments "
- A Macintosh Point indicating the offset from the position of the node box to
- the center of the Bottom edge of the box.")
-
- (LEFT-CENTER 0
- :type fixnum
- :computed t
- :comments "
- A Macintosh Point indicating the offset from the position of the node box to
- the center of the Left edge of the box.")
-
- (RIGHT-CENTER 0
- :type fixnum
- :computed t
- :comments "
- A Macintosh Point indicating the offset from the position of the node box to
- the center of the Right edge of the box.")
-
- (CENTER 0
- :type fixnum
- :computed t
- :comments "
- A Macintosh Point indicating the offset from the position of the node box to
- the center of the box. Mouse sensitivity is computed relative to this."))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (sm:dst (GRAPH-VIEW
- (:reusable nil)
- (:sort-instances t)
- (:comments "
- A Graph-View is a view (way of drawing graphically) a portion of a graph
- (collection of Graph-Nodes). A given Graph-Node may participate in multiple
- Graph-Views, and a given graph may have multiple views on it. The Graph-View
- is specified in terms of the Graph-Nodes which are the Roots to search the
- graph from, the Depth-Bound of this search, the Style of laying out the found
- graph, and the Ordering used to layout children of a given node. Not reusable
- since the members list should be reclaimed."))
-
- (ROOTS nil
- :type list
- :comments "
- A list of Graph-Nodes from which to search in constructing the view.")
-
- (DEPTH-BOUND 9
- :type (integer 0 *)
- :comments "
- How deep from the roots to search in the graph to construct the view.")
-
- (STYLE :horizontal-tree*
- :type (member :horizontal-tree :vertical-tree
- :horizontal-tree* :vertical-tree*
- :radial :radial*)
- :comments "
- A keyword indicating the style in which the graph is drawn.")
-
- (ORDERING :reduce-crossings
- :type (member :as-found :reverse-as-found
- :reduce-crossings :reverse-reduce-crossings)
- :comments "
- Keyword indicationg how to order the children of a node in the graph.")
-
- (NODE-FONT '("monaco" 9)
- :type list
- :comments "
- Font for labeling the node boxes.")
-
- (TEXT-FONT '("chicago" 12)
- :type list
- :comments "
- Font used when writing text into the graph window (other than node labels).")
-
- (BORDER-WIDTH 10
- :type fixnum
- :comments "
- This many points are left white as leading edge before drawing.")
-
- (INFO nil
- :type t
- :comments "
- User may place comments, derivation trace, or other info here.")
-
- (MOUSE-METHODS
- '(("Show Graph View Parameters"
- . (lambda (gw gv gn)
- (let ((gv-struct (sm:gets 'graph-view gv))
- (*print-case* :downcase))
- (oneof *dialog*
- :window-title (format nil "~S Graph View Parameters" gv)
- :window-position (add-points (make-point 10 10)
- (ask gw (window-position)))
- :window-size #@(570 280)
- :window-type :document
- :default-button nil
- :close-box-p t
- :dialog-items
- (list
- (oneof *editable-text-dialog-item*
- :dialog-item-text
- (format nil "Parameters for Graph View ~S ...~%~
- ~%* ROOTS: ~S~
- ~%* DEPTH BOUND: ~S~
- ~%* STYLE: ~S~
- ~%* ORDERING: ~S~
- ~%* INFO:~% (~{~S~% ~})"
- gv
- (graph-view-roots gv-struct)
- (graph-view-depth-bound gv-struct)
- (graph-view-style gv-struct)
- (graph-view-ordering gv-struct)
- (graph-view-info gv-struct))
- :dialog-item-position #@(5 5)
- :dialog-item-size #@(560 270)
- :allow-returns nil))))))
- ("Inspect Associated Object"
- . (lambda (gw gv gn)
- (inspect (graph-node-object (sm:gets 'graph-node gn)))))
- ("Inspect Graph Node Itself"
- . (lambda (gw gv gn)
- (inspect (sm:gets 'graph-node gn)))))
- :type T
- :comments "
- The Mouse-Methods may be either a functional object of three arguments, or an
- association list. If a function, it is called on the graph-window, graph-view,
- and graph-node involved in the mouse click. Association lists should map labels
- (symbols or strings) to mouse methods, which are lambdas of three arguments (to
- be given the same arguments as for the functions). When the user clicks twice on
- a node, a menu of the domain of the alist is put up, and the lambda which is the
- image of the selected item is called. The lambdas may be compiled, but they are
- not saved by SAVE-GRAPH-VIEW. (The default methods are not compiled, and are
- saved by that function.)")
-
- (MEMBERS nil
- :type list
- :computed t
- :comments "
- Association list of Graph-Nodes to CL node-placement structures, which say how
- deep the node is in the DAG, and where to place it on the screen. Also defines
- who is in the Graph-View.")
-
- (LEVELS 0
- :type (integer 0 *)
- :computed t
- :comments "
- How deep the graph goes beyond the root nodes (<= depth bound)."))
-
- ;;; This defines the placement of a graph-node in a view. These records
- ;;; are accessed as the image of a graph node in a graph-view-members list.
- ;;; Totally computed, so need not use SM.
-
- (defstruct (NODE-PLACEMENT (:type vector))
- (level 0 :type integer) ; depth + 1 in the DAG for this view
- (position 0 :type integer) ; position to be printed in *graph-window*
- (quadrant :na ; in reference to diagonal axes
- :type (member :na :upper :lower :left :right)))
-
- (defmacro GRAPH-VIEW-INFO-IMAGE (key gv)
- `(cdr (assoc ,key (graph-view-info (sm:gets 'graph-view ,gv)))))
-
- (defun DISPOSE-GRAPH-VIEW (graph-view)
- "dispose-graph-view [Function]
- Destroys the graph view indicated, including any known graph-node members
- of the view which are not being used elsewhere. Checks for whether
- graph-node members have already been destroyed. Non-nil if disposed."
- (when (sm:gets 'graph-view graph-view)
- (let ((members (graph-view-members (sm:gets 'graph-view graph-view))))
- (declare (list members) (optimize (safety 1) (space 2) (speed 3)))
- ;; This is done as soon as possible so gone if error leaves it in bad state.
- (sm:destroys 'graph-view graph-view)
- (dolist (gn+pos members) (declare (cons gn+pos))
- (let ((gn (car gn+pos))) (declare (symbol gn))
- ;; Watch for already-deleted members
- (if (sm:gets 'graph-node gn)
- ;; Don't delete if being used by another graph-view
- (unless (dolist (gv (sm:instances 'graph-view)) (declare (symbol gv))
- (if (assoc gn (graph-view-members (sm:gets 'graph-view gv)))
- (return T)))
- (sm:destroys 'graph-node gn))))))
- graph-view))
-
- ;;; The price of shared external graph nodes is all this looking around to see
- ;;; who I can collect. Provide this function for external use.
-
- (defun GRAPH-VIEWS-USING-GRAPH-NODE (gn &aux (results nil))
- (dolist (gv (sm:instances 'graph-view))
- (declare (symbol gv) (optimize (safety 1) (space 2) (speed 3)))
- (if (assoc gn (graph-view-members (sm:gets 'graph-view gv)))
- (push gv results)))
- results)
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; GRAPH WINDOW OBJECTS & BASIC METHODS
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (defobject *GRAPH-WINDOW* *window*)
-
- (defobfun (EXIST *graph-window*) (init-list)
- (declare (object-variable graph-view))
- (let ((graph-view (getf init-list :graph-view nil)))
- (check-type graph-view symbol)
- ;; These must be done first in case :window-show is specified T by user.
- ;; That would cause drawing to occur, which expects these to be "had".
- (have 'graph-view graph-view)
- (have 'selected-node nil)
- ;; Usual-exist should not show the window because layout has not been
- ;; computed yet. Cannot compute layout before usual-exist, because wptr
- ;; is not bound yet. So: usual-exist without show; do layout; then show.
- (usual-exist
- (init-list-default
- init-list
- :window-title (sm:prints 'graph-view graph-view :style :name :stream nil)
- :window-position (make-point *screen-edge-indentation* *menubar-bottom*)
- :window-size (make-point (- *screen-width*
- (* 2 *screen-edge-indentation*))
- (- *screen-height* *menubar-bottom*
- *screen-edge-indentation*))
- :window-show nil
- :window-font '("monaco" 9)
- :window-type :document-with-zoom
- :close-box t))
- (layout-graph-view)
- ;; The desired behavior is default of show T, but user can override
- ;; whether shown at all. Unfortunately, if user specifies show T
- ;; explicitly, it will be shown before layout in usual-exist.
- (if (getf init-list :window-show t) (window-show))
- ;; Return object created.
- (self)))
-
- (defobfun (SET-GRAPH-VIEW *graph-window*) (view-asked-for &key (layout t))
- "set-graph-view <view-asked-for> &key <layout> [Graph Window Function]
- Sets the graph view associated with the window. Computes its layout,
- unless <layout> is nil. Does not select or redraw the window."
- (declare (object-variable graph-view selected-node))
- (if view-asked-for ; NIL is allowed.
- (assert (sm:gets 'graph-view view-asked-for) (view-asked-for)
- "[SET-GRAPH-VIEW] Unknown graph view ~S" view-asked-for))
- (setq graph-view view-asked-for)
- (setq selected-node nil)
- ;; Layout must be (re)computed to fit this window.
- (if layout (layout-graph-view))
- view-asked-for)
-
- (defobfun (SET-WINDOW-SIZE *graph-window*) (new-size)
- "set-window-size [Graph Window Function]
- Calls usual set-window-size, but then redoes the layout of its graph
- view and recalls view-draw-contents. In order to prevent the view
- from being drawn twice, binds the graph-view to nil during the set,
- since it also calls view-draw-contents. Selected-node is reset."
- (declare (object-variable graph-view selected-node))
- (check-type new-size fixnum)
- ;; Check for bad graph view (may have been destroyed).
- (if (null (sm:gets 'graph-view graph-view))
- (setf graph-view nil))
- ;; Bind graph view to nil to prevent drawing, and change size by
- ;; calling usual function.
- (let ((graph-view nil))
- (funcall (ask *window* (symbol-function 'set-window-size)) new-size))
- ;; Reset selected node; then do layout and drawing.
- (setq selected-node nil)
- (layout-graph-view)
- (view-draw-contents))
-
- (defobfun (WINDOW-SELECT *graph-window*) ()
- "window-select [Graph Window Function]
- Selects the window, and if there is a non-nil graph-view, draws it (via
- view-draw-contents). No node will be selected. If the window names
- a graph view which no longer exists, the window's view is set to nil."
- (declare (object-variable graph-view selected-node))
- ;; Check for bad graph view (may have been destroyed).
- (if (null (sm:gets 'graph-view graph-view))
- (setf graph-view nil))
- ;; Reset selected node, and invoke usual method, which calls
- ;; view-draw-contents.
- (setq selected-node nil)
- (funcall (ask *window* (symbol-function 'window-select))))
-
- (defun WINDOWS-USING-GRAPH-VIEW (target-graph-view &aux (result ()))
- (declare (object-variable graph-view) (optimize (safety 1) (space 2) (speed 3)))
- (dolist (w (windows *graph-window*))
- (if (ask w (eq target-graph-view graph-view))
- (push w result)))
- result)
-
- ;;; Closeness of click to node measured using this.
-
- (eval-when (eval compile)
- (defmacro MANHATTAN-DISTANCE (arg1 arg2)
- ;; The manhattan distance is how far you walk on a discrete grid to get from
- ;; one point to another. It is more efficient to compute than euclidean
- ;; distance, and, when distances are needed for comparative purposes only,
- ;; just as good.
- `(let ((point1 ,arg1) (point2 ,arg2))
- (declare (fixnum point1 point2) (optimize (safety 1) (space 2) (speed 3)))
- (+ (abs (- (point-h point1) (point-h point2)))
- (abs (- (point-v point1) (point-v point2))))))
- )
-
- ;;; This has to be a function! When macro expansion includes object variable
- ;;; (graph-view), things are unpredictable. Believe it or not, sometimes the
- ;;; variable was accessed correctly, and sometimes it yielded NIL.
-
- (defun RUN-MOUSE-METHOD (gw gv gn)
- (declare (object-variable graph-view))
- (let ((mouse-methods (graph-view-mouse-methods (sm:gets 'graph-view gv))))
- (if (typep mouse-methods 'function)
- (funcall mouse-methods gw gv gn)
- ;; Here we home-brew a menu so we can avoid redraw if needed.
- (let* ((message-item
- (oneof *static-text-dialog-item*
- :dialog-item-text
- (format nil "What do you want to do with ~S?"
- (let ((gn-object (graph-node-object
- (sm:gets 'graph-node gn))))
- (if gn-object gn-object gn)))
- :dialog-item-size (make-point 450 22)))
- (menu-item
- (oneof *sequence-dialog-item*
- :dialog-item-position (make-point 5 32)
- :dialog-item-size (make-point 350 142)
- :cell-size (make-point 350 16)
- :table-vscrollp t
- :visible-dimensions (make-point 1 7)
- :table-sequence (mapcar #'car mouse-methods)
- :dialog-item-action
- #'(lambda ()
- (if (double-click-p)
- (if (selected-cells)
- (let ((act (cell-contents (first (selected-cells)))))
- ;; Nullify graph view if it is about to be destroyed.
- (if (member act *mouse-methods-replacing-graph-view*
- :test #'equal)
- (ask gw (setq graph-view nil)))
- (return-from-modal-dialog act))
- (ed-beep))))))
- (ok-button
- (oneof *button-dialog-item*
- :dialog-item-text " OK "
- :dialog-item-position
- (make-point 405 42)
- :dialog-item-action
- #'(lambda ()
- (ask menu-item
- ;; This code duplicates menu-item action.
- (if (selected-cells)
- (let ((act (cell-contents (first (selected-cells)))))
- ;; Nullify graph view if it is about to be destroyed.
- (if (member act *mouse-methods-replacing-graph-view*
- :test #'equal)
- (ask gw (setq graph-view nil)))
- (return-from-modal-dialog act))
- (ed-beep))))
- :default-button t))
- (cancel-button
- (oneof *button-dialog-item*
- :dialog-item-text "CANCEL"
- :dialog-item-position
- (make-point 395 82)
- :dialog-item-action
- #'(lambda () (return-from-modal-dialog :cancel))))
- (the-dialogue
- (oneof *dialog*
- :window-title "Mouse Method Menu Dialogue"
- :window-position (upper-left-popup-position gw)
- :window-size (make-point 480 170)
- :window-show t
- :window-type :double-edge-box
- :dialog-items (list ok-button cancel-button message-item menu-item))))
- ;; Select a default action.
- (ask menu-item (cell-select (index-to-cell 0)))
- ;; Get the action: execute it.
- (funcall (cdr (assoc (modal-dialog the-dialogue) mouse-methods :test #'equal))
- gw gv gn)))))
-
- (defobfun (ccl::WINDOW-CLICK-EVENT-HANDLER *graph-window*) (mouse-position)
- (declare (fixnum mouse-position)
- (object-variable graph-view selected-node))
- ;; Actions are specific to graph views, so we don't process unless there is a
- ;; graph view. Loop to find the node with the smallest manhattan distance
- ;; from the mouse, and perform the appropriate operation on that node.
- (when graph-view
- (do ((gn+p-ptr (graph-view-members (sm:gets 'graph-view graph-view))
- (cdr gn+p-ptr))
- (nearest-node nil)
- (smallest-distance *screen-width*) ; a big number to start with
- (distance 0))
- ;; Exit: ensure the click was close enough.
- ((null gn+p-ptr)
- (if (< smallest-distance *graph-window-double-click-spacing*)
- ;; Close enough to identify a nearest-node. Primary action depends on
- ;; what is already selected. If user is holding mouse down (wait a bit
- ;; to see), will move the node. When moving, the primary action is done
- ;; only if necessary to select the node to be moved.
- (cond ((null selected-node)
- ;; First click on this node and no other node selected: highlight.
- (setq selected-node nearest-node)
- (invert-node selected-node)
- (if (progn (sleep *graph-window-mouse-down-delay*) (mouse-down-p))
- (move-node selected-node)))
- ((eq selected-node nearest-node)
- ;; Second click on selected: move OR do mouse method, depending on
- ;; whether mouse held. (Both would be too confusing).
- (if (progn (sleep *graph-window-mouse-down-delay*) (mouse-down-p))
- (move-node selected-node)
- (run-mouse-method (ccl:front-window) ; ccl:self didn't work!
- graph-view selected-node)))
- (T
- ;; Node clicked differs from node highlighted: change selected.
- (invert-node selected-node)
- (invert-node nearest-node)
- (setq selected-node nearest-node)
- (if (progn (sleep *graph-window-mouse-down-delay*) (mouse-down-p))
- (move-node selected-node))))
- ;; Click is in whitespace: turn off any selected node, or redraw contents
- ;; if this is the second of a double click (first click deselected node).
- (if selected-node
- (progn (invert-node selected-node) (setq selected-node nil))
- (if (double-click-p) (view-draw-contents)))))
- (declare (list gn+p-ptr) (fixnum smallest-distance distance)
- (optimize (safety 1) (space 2) (speed 3)))
- ;; Find the manhattan distance from the mouse click to the center of this node.
- (setf distance
- (manhattan-distance
- mouse-position
- (add-points (graph-node-center (sm:gets 'graph-node (caar gn+p-ptr)))
- (node-placement-position (cdar gn+p-ptr)))))
- ;; Record it as the closest so far if closer than the previous closest node.
- (when (< distance smallest-distance)
- (setf nearest-node (caar gn+p-ptr))
- (setf smallest-distance distance)))))
-
- (defobfun (INVERT-NODE *graph-window*) (node)
- (declare (object-variable graph-view))
- ;; Assumes it is already drawn; inverts the thing.
- (let* ((node+placement
- (assoc node (graph-view-members (sm:gets 'graph-view graph-view))))
- (node-placement-position (node-placement-position (cdr node+placement)))
- (box-size (graph-node-box-size (sm:gets 'graph-node (car node+placement)))))
- (declare (cons node+placement) (list node-font)
- (fixnum node-placement-position box-size))
- ;; Invert everything in the box outline region.
- (case (graph-node-box-style (sm:gets 'graph-node (car node+placement)))
- ((:none :rect :frame :none-frame)
- (invert-rect node-placement-position
- (add-points node-placement-position box-size)))
- ((:round-rect :round-frame)
- (invert-round-rect
- (point-v box-size)
- (point-v box-size)
- node-placement-position
- (add-points node-placement-position box-size)))
- ((:oval)
- (invert-oval node-placement-position
- (add-points node-placement-position box-size))))))
-
- (defobfun (MOVE-NODE *graph-window*) (node)
- (declare (object-variable graph-view selected-node))
- ;; Tracks the mouse and repositions <node> when and where the mouse is released.
- ;; Placement is such that the mouse points to the CENTER of the box position.
- (let ((node+placement
- (assoc node
- (graph-view-members (sm:gets 'graph-view graph-view))))
- (time (get-internal-run-time))
- (center-offset
- (graph-node-center (sm:gets 'graph-node selected-node))))
- (declare (cons node+placement) (fixnum time center-offset)
- (optimize (safety 1) (space 2) (speed 3)))
- ;; Start with this to improve apparent response time. (We already had
- ;; a 0.5 sec. delay in the calling function.)
- (draw-graph-node-outline node center-offset)
- (loop
- ;; Draw outline occasionally. This lets user see where it will go.
- ;; If drawn too often, the screen will get cluttered since we are
- ;; not erasing previous outlines.
- (when (> (- (get-internal-run-time) time)
- *outline-time-interval*)
- (draw-graph-node-outline node center-offset)
- (setq time (get-internal-run-time)))
- ;; Reposition node, redraw window, and exit when mouse released.
- (unless (mouse-down-p)
- (setf (node-placement-position (cdr node+placement))
- (subtract-points (window-mouse-position) center-offset))
- (return (view-draw-contents))))))
-
- (defobfun (DRAW-GRAPH-NODE-OUTLINE *graph-window*) (node center-offset)
- (let* ((node-struct (sm:gets 'graph-node node))
- (box-style (graph-node-box-style node-struct))
- (box-size (graph-node-box-size node-struct))
- (position (subtract-points (window-mouse-position) center-offset)))
- (declare (type graph-node node-struct) (keyword box-style)
- (fixnum box-size position) (optimize (safety 1) (space 2) (speed 3)))
- ;; Draw box outline.
- (set-pen-pattern *light-gray-pattern*)
- (case box-style
- ((:none :rect :none-frame :frame)
- (frame-rect
- position
- (add-points position box-size)))
- ((:round-rect :round-frame)
- (frame-round-rect
- (point-v box-size)
- (point-v box-size)
- position
- (add-points position box-size)))
- ((:oval)
- (frame-oval
- position
- (add-points position box-size))))
- (pen-normal)))
-
- (defobfun (ccl::WINDOW-KEY-EVENT-HANDLER *graph-window*) (char)
- (declare (object-variable graph-view selected-node))
- ;; Interprets arrow keys to move around in the graph.
- (when graph-view
- (case char
- ((#\ #\) ; down and right arrows: make selected node the root.
- (let ((method (assoc "make this node the root"
- (graph-view-mouse-methods (sm:gets 'graph-view graph-view))
- :test #'string-equal)))
- (if (and selected-node method)
- (funcall (cdr method) (self) graph-view selected-node)
- (ccl:ed-beep))))
- ((#\ #\) ; left and up arrows: return to previous view.
- (let ((method (assoc "backup once to parent view"
- (graph-view-mouse-methods (sm:gets 'graph-view graph-view))
- :test #'string-equal)))
- (if method
- (funcall (cdr method) (self) graph-view selected-node)
- (ccl:ed-beep)))))))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; GRAPH LAYOUT MACROS
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (eval-when (compile eval)
-
- (defmacro LAYER-SPACE-FOR-STYLE (layout-style border-width)
- ;; Computes how much space is available in the direction the layers are drawn.
- ;; Vertical tree is centered in layers and fixed v-size, so no border off.
- `(ecase ,layout-style
- ((:vertical-tree :vertical-tree*)
- (point-v (window-size)))
- ((:horizontal-tree :horizontal-tree*)
- (- (point-h (window-size)) (* 2 ,border-width)))
- ((:radial :radial*)
- (truncate (/ (float (- (min (point-v (window-size))
- (point-h (window-size)))
- (* 2 ,border-width)))
- 2.0)))))
-
- (defmacro SIBLING-SPACE-FOR-STYLE (layout-style border-width)
- ;; Computes how much space is available in the direction siblings are drawn.
- `(ecase ,layout-style
- ((:vertical-tree :vertical-tree*)
- (- (point-h (window-size)) (* 2 ,border-width)))
- ((:horizontal-tree :horizontal-tree*)
- (- (point-v (window-size)) (* 2 ,border-width)))
- ;; White lie: give them radians to work with; will convert to
- ;; cartesian later. (Without angular measure, space available to
- ;; siblings would vary with depth). There are 6.28 radians in a
- ;; circle: using 62800 to faciliate fast but accurate integer math.
- ((:radial :radial*) 62800)))
-
- (defmacro EQUALLY-ALLOCATED-SPACE (space-available number-of-contendors)
- ;; Determine how much space each of n contendors gets.
- `(let ((denominator ,number-of-contendors)
- (numerator ,space-available))
- (if (= denominator 0)
- numerator
- ;; Rounding up does risk overflow, but only in graphs with an absurd
- ;; number of elements. I tried all kinds of things here; this is best.
- (ceiling (/ (float numerator)
- (float denominator))))))
-
- ;;; Find start point of range given which range and the number of points per range.
- (defmacro RANGE-START (range points) `(* ,range ,points))
-
- ;;; Find center point of range given which range and the number of points per range.
- (defmacro RANGE-CENTER (range points)
- `(truncate (* (+ 0.5 (float ,range)) (float ,points))))
-
- ;;; Find end point of range given which range and the number of points per range.
- (defmacro RANGE-END (range points) `(* (+ 1 ,range) ,points))
-
- ;;; Center a box at a given horizontal coordinate, taking box width into account.
- (defmacro CENTER-BOX-AT-H (box-size h-place)
- `(- ,h-place
- (truncate (/ (float (point-h ,box-size)) 2.0))))
-
- ;;; Center a box at a given vertical coordinate, taking box height into account.
- (defmacro CENTER-BOX-AT-V (box-size v-place)
- `(- ,v-place
- (truncate (/ (float (point-v ,box-size)) 2.0))))
-
- )
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; GRAPH LAYOUT AND DRAWING METHODS
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- ;;; Apologies for the oversized function. Just couldn't break it up nice.
-
- (defobfun (LAYOUT-GRAPH-VIEW *graph-window*) ()
- "layout-graph-view [Graph Window Function]
- Computes the layout for a graph-window's graph view, without displaying it.
- In this simple version, we assume a fixed finite window size, and try to
- fit everything in. (If it doesn't fit, the user may create sub-views and
- move between them.) The layers are each given an equal amount of space
- in one dimension (which depending on whether it is a horizontal, vertical,
- or radial style), as is each node within each layer in the other dimension.
- In the layer direction, nodes are placed at the start of the range allocated
- to that layer. In the within-layer, node direction, each node is centered
- in the range allocated to it."
- (declare (object-variable graph-view window-title))
-
- (when graph-view ; NIL represents the empty graph, so nothing is done in that case.
- (assert (sm:gets 'graph-view graph-view) (graph-view)
- "[GRAPHER:LAYOUT-GRAPH-VIEW] Graph Window ~S has bad graph view ~S."
- window-title graph-view)
-
- (let* ((graph-view-struct (sm:gets 'graph-view graph-view))
- (layout-style (graph-view-style graph-view-struct))
- (view-members (compute-graph-view-members
- graph-view
- layout-style
- (graph-view-ordering graph-view-struct)))
- (border-width (graph-view-border-width graph-view-struct))
- (total-sibling-space (sibling-space-for-style layout-style border-width))
- (layer-spacing (equally-allocated-space
- (layer-space-for-style layout-style border-width)
- (graph-view-levels graph-view-struct))))
- (declare (type graph-view graph-view-struct)
- (keyword layout-style) (list vertical-format-p view-members)
- (fixnum border-width total-sibling-space layer-spacing)
- (optimize (safety 1) (space 2) (speed 3)))
-
- ;; Ensure that graph-independent node parameters have been computed.
- (dolist (node+placement view-members)
- (declare (cons node+placement))
- (let* ((node-struct (sm:gets 'graph-node (car node+placement)))
- (node-font (graph-view-node-font graph-view-struct))
- (box-style (graph-node-box-style node-struct))
- ;; A bit messy, but empirically the padding depends on font and style.
- (box-height (multiple-value-bind
- (a d) (font-info node-font)
- (if (member box-style *framed-styles*)
- (* (length (graph-node-label node-struct))
- (+ a d (if (< (second node-font)
- *small-font-threshold*)
- *small-font-box-height-padding*
- *large-font-box-height-padding*)))
- (+ a d (ecase box-style
- ((:none :rect :round-rect)
- (if (< (second node-font)
- *small-font-threshold*)
- *small-font-box-height-padding*
- *large-font-box-height-padding*))
- ((:oval)
- (if (< (second node-font)
- *small-font-threshold*)
- *small-font-oval-box-height-padding*
- *large-font-oval-box-height-padding*)))))))
- (box-half-height (truncate (/ (float box-height) 2.0)))
- (box-width (+ (if (member box-style *framed-styles*)
- (apply #'max
- (mapcar #'(lambda (s) (declare (string s))
- (string-width s node-font))
- (graph-node-label node-struct)))
- (string-width (graph-node-label node-struct) node-font))
- (ecase box-style ; padding required for each style
- ((:none :rect :none-frame :frame) *rect-box-width-padding*)
- ((:round-rect) *round-rect-box-width-padding*)
- ((:round-frame) *round-frame-box-width-padding*)
- ((:oval) *oval-box-width-padding*))))
- (box-half-width (truncate (/ (float box-width) 2.0))))
- (declare (type graph-node node-struct) (list node-font) (keyword box-style)
- (fixnum box-height box-half-height box-width box-half-width))
- (setf (graph-node-box-size node-struct)
- (make-point box-width box-height))
- (setf (graph-node-top-center node-struct)
- (make-point box-half-width 0))
- (setf (graph-node-bottom-center node-struct)
- (make-point box-half-width box-height))
- (setf (graph-node-left-center node-struct)
- (make-point 0 box-half-height))
- (setf (graph-node-right-center node-struct)
- (make-point box-width box-half-height))
- (setf (graph-node-center node-struct)
- (make-point box-half-width box-half-height))))
-
- ;; Iterate over levels (depth 0 = level 1) to layout nodes in each level.
- (dotimes (depth (graph-view-levels graph-view-struct))
- (declare (fixnum depth))
- (let* ((level-nodes+placements
- (ordered-nodes+placements-at-level
- view-members (1+ depth) (graph-view-ordering graph-view-struct)))
- (sibling-spacing
- (equally-allocated-space total-sibling-space
- (length level-nodes+placements))))
- (declare (list level-nodes+placements) (fixnum sibling-spacing))
-
- ;; Iterate over nodes in this level to compute the placements.
- (do* ((n+pptr level-nodes+placements (rest n+pptr))
- (node-number 0 (1+ node-number)))
- ((null n+pptr))
- (declare (list n+pptr) (fixnum node-number))
- (let* ((node+placement (car n+pptr))
- (box-size (graph-node-box-size
- (sm:gets 'graph-node (car node+placement)))))
- (declare (cons node+placement) (fixnum box-size))
- (case layout-style
- ;; In all layouts, the box is placed at the center of its within-level
- ;; allocated space, but at the beginning of its level's range. Trees
- ;; differ on which is horizontal and which vertical.
- ((:vertical-tree :vertical-tree*)
- (setf (node-placement-position (cdr node+placement))
- (make-point
- (+ border-width
- (center-box-at-h box-size
- (range-center node-number sibling-spacing)))
- (center-box-at-v box-size
- (range-center depth layer-spacing)))))
- ;; Not centering layers, since would be ragged, so shift two borders
- ;; worth to make up for it.
- ((:horizontal-tree :horizontal-tree*)
- (setf (node-placement-position (cdr node+placement))
- (make-point
- (+ (* 2 border-width) (range-start depth layer-spacing))
- (+ border-width
- (center-box-at-v box-size
- (range-center node-number sibling-spacing))))))
- ;; Parameters for :radial similar to :vertical-tree, but placement and
- ;; centering is interspersed with coordinate conversion. First find its
- ;; place, which is the center of its allocated radians (p) and radius (r)
- ;; ranges. Then convert. THEN center the actual box over its place, so
- ;; adjustment is in the relevant dimensions.
- ((:radial :radial*)
- (let* ((radial-p (/ (float (range-center node-number sibling-spacing))
- 10000.0))
- (radial-r (if (= depth 0) 0.0 (float (range-end depth layer-spacing))))
- (cartesian-h (+ (round (* radial-r (cos radial-p)))
- (round (/ (float (point-h (window-size))) 2.0))))
- (cartesian-v (+ (round (* radial-r (sin radial-p)))
- (round (/ (float (point-v (window-size))) 2.0)))))
- (declare (float radial-p radial-r) (fixnum cartesian-h cartesian-v))
- ;; Need this info later to determine attachment points. Prefer top
- ;; or bottom attachements = upper or lower classifications.
- (setf (node-placement-quadrant (cdr node+placement))
- (cond ((< radial-p (utils:radians 30.0)) :right)
- ((< radial-p (utils:radians 150.0)) :lower)
- ((< radial-p (utils:radians 210.0)) :left)
- ((< radial-p (utils:radians 330.0)) :upper)
- (T :right)))
- (setf (node-placement-position (cdr node+placement))
- (make-point (center-box-at-h box-size cartesian-h)
- (center-box-at-v box-size cartesian-v)))))))))))))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; DRAWING
- ;;; I used to use pictures, but dumped it due to bugs. I used to get:
- ;;; Picture for window: #<Object #167, MacGrapher, a *WINDOW*> is not started
- ;;; even though I called (start-picture) unconditionally. Now, the only
- ;;; computation we can save is not calling the layout-graph-view twice.
-
- (defobfun (VIEW-DRAW-CONTENTS *graph-window*) ()
- "view-draw-contents [Graph Window Function]
- Draws a graph-window's graph view. It must have a graph view, and you
- must invoke layout-graph-view at least once before drawing: see that
- method's documentation."
- (declare (object-variable graph-view selected-node window-title))
-
- ;; Nil is now the representation of the empty graph view. We clear the window
- ;; first no matter what happens, so if graph-view is nil no drawing need be done.
- (erase-region (clip-region))
- (when graph-view
- (assert (sm:gets 'graph-view graph-view) (graph-view)
- "Graph Window ~S has bad graph view ~S."
- window-title graph-view)
-
- (let* ((graph-view-struct (sm:gets 'graph-view graph-view))
- (layout-style (graph-view-style graph-view-struct))
- (view-members (graph-view-members graph-view-struct)))
- (declare (type graph-view graph-view-struct)
- (keyword layout-style) (list view-members)
- (optimize (safety 1) (space 2) (speed 3)))
-
- ;; Draw lines to all children for each node+placement in the view-members.
- ;; Draw lines first, so when boxes drawn they are in front of lines.
- (dolist (node+placement view-members)
- (declare (cons node+placement))
- (dolist (child (graph-node-children (sm:gets 'graph-node (car node+placement))))
- (declare (symbol child))
- ;; Filter out children that were not included in the view.
- (let ((child-node+placement (assoc child view-members)))
- (declare (cons child-node+placement))
- (if child-node+placement
- (draw-parent-to-child-link
- node+placement child-node+placement layout-style)))))
-
- ;; Then draw the nodes themselves, and invert the selected node.
- (set-window-font (graph-view-node-font graph-view-struct))
- (dolist (node+placement view-members)
- (declare (cons node+placement))
- (draw-graph-node node+placement (graph-view-node-font graph-view-struct)))
- (set-window-font (graph-view-text-font graph-view-struct))
- (if selected-node (invert-node selected-node))
-
- ;; Put the grow icon back in, and Return the view drawn.
- (window-draw-grow-icon)
- graph-view)))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; GRAPH VIEW COMPUTATIONS
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- ;;; Given a graph object, compute the members of the graph.
- ;;; In the process, check for cycles.
- ;;; Terminology: the root is at "depth" 0 but "level" 1.
-
- (defun COMPUTE-GRAPH-VIEW-MEMBERS (graph-view layout-style ordering)
- (declare (symbol graph-view layout-style ordering))
- (let* ((*graph-view-struct* (sm:gets 'graph-view graph-view))
- (*active-nodes* nil)
- (*depth-bound* (graph-view-depth-bound *graph-view-struct*)))
- (declare (special *graph-view-struct* *active-nodes* *depth-bound*)
- (type graph-view *graph-view-struct*)
- (list *active-nodes*) (integer *depth-bound*)
- (optimize (safety 1) (space 2) (speed 3)))
-
- ;; Two helpers for ordering children, then a recursive function
- ;; that does the work ...
-
- (defun REORDER-TO-MINIMIZE-CROSSINGS (children)
- ;; Prefers to do first children which themselves have children already
- ;; in the graph, and resolves between multiple such children by doing
- ;; the one with the earliest (first visited) grandchild first. This
- ;; reduces the number of children that have to link across other
- ;; children's links to get to their children.
- (declare (list children))
- (mapcar #'car
- (stable-sort (mapcar #'(lambda (node)
- (declare (symbol node))
- (cons node (earliest-child-placement
- (graph-node-children
- (sm:gets 'graph-node node)))))
- children)
- #'(lambda (f1 f2)
- (declare (cons f1 f2))
- (<= (cdr f1) (cdr f2))))))
-
- (defun EARLIEST-CHILD-PLACEMENT (children)
- (declare (list children) (special *graph-view-struct*))
- (let ((smallest most-positive-fixnum))
- (declare (fixnum smallest))
- (dolist (child children)
- (declare (symbol child))
- ;; This depends on order being preserved in the members list.
- (let ((child-score (or (position child
- (graph-view-members *graph-view-struct*)
- :key #'car)
- most-positive-fixnum)))
- (declare (fixnum child-score))
- (if (<= child-score smallest) (setf smallest child-score))))
- smallest))
-
-
- (defun VISIT-NODE-AND-RECURSE (node current-depth)
- ;; Adds the node or updates its depth; and then recurses on children.
- (declare (special *graph-view-struct* *active-nodes* *depth-bound*)
- (symbol node) (integer current-depth)
- (optimize (safety 1) (space 2) (speed 3)))
- ;; Don't do it if we've found our way back via a cycle, or have gone too deep.
- (unless (or (> current-depth *depth-bound*)
- (member node *active-nodes*))
- (let ((node-placement
- (utils:image node (graph-view-members *graph-view-struct*))))
- (declare (vector node-placement))
- (if node-placement
- ;; If the node has been visited, update its level to the deeper one.
- (setf (node-placement-level node-placement)
- (max (1+ current-depth) (node-placement-level node-placement)))
- ;; Otherwise add a new view-member record for this newly visited node.
- ;; (I had a push here, but it was reversing the graphed order of children.)
- ;; NOTE I rely on this ordering in earliest-child-placement, above!
- (setf (graph-view-members *graph-view-struct*)
- (nconc (graph-view-members *graph-view-struct*)
- (list (cons node
- (make-node-placement :level (1+ current-depth))))))))
- ;; Update max depth of graph; Recurse to add children if not too deep.
- (setf (graph-view-levels *graph-view-struct*)
- (max (graph-view-levels *graph-view-struct*) (1+ current-depth)))
- (unless (> (1+ current-depth) *depth-bound*)
- (push node *active-nodes*)
- (let ((children (graph-node-children (sm:gets 'graph-node node))))
- (declare (list children))
- (if (member ordering
- '(:reduce-crossings :reverse-reduce-crossings))
- (loop
- (when (null children) (return))
- (setf children (reorder-to-minimize-crossings children))
- (visit-node-and-recurse (pop children) (1+ current-depth)))
- (dolist (child children)
- (declare (symbol child))
- (visit-node-and-recurse child (1+ current-depth)))))
- (pop *active-nodes*))))
-
- ;; Prepare for recomputation of members list.
- (setf (graph-view-members *graph-view-struct*) nil)
-
- ;; The * styles collect childless roots and put them in the first
- ;; level, starting roots with children at level 2 (where depth 1
- ;; normally would be). Determine if this is needed; call the helper
- ;; function on each of the roots with the appropriate starting depth.
- (if (and (member layout-style *starred-styles*)
- (some #'(lambda (root)
- (null (graph-node-children (sm:gets 'graph-node root))))
- (graph-view-roots *graph-view-struct*)))
- (dolist (root (graph-view-roots *graph-view-struct*))
- (declare (symbol root))
- (if (null (graph-node-children (sm:gets 'graph-node root)))
- (visit-node-and-recurse root 0)
- (visit-node-and-recurse root 1)))
- (dolist (root (graph-view-roots *graph-view-struct*))
- (declare (symbol root))
- (visit-node-and-recurse root 0)))
-
- ;; It is possible for all roots initially placed in column 1 to be
- ;; reached by alternate paths and pushed down. Check for this
- ;; condition and shift everyone up if needed.
- (when (notany #'(lambda (node+placement)
- (declare (cons node+placement))
- (= 1 (node-placement-level (cdr node+placement))))
- (graph-view-members *graph-view-struct*))
- (dolist (node+placement (graph-view-members *graph-view-struct*))
- (decf (node-placement-level (cdr node+placement))))
- (decf (graph-view-levels *graph-view-struct*)))
-
- ;; Return the computed members, stored in the structure as well.
- (graph-view-members *graph-view-struct*))
- )
-
- ;;; This extracts node+placement records at a level, and orders it in
- ;;; ways which affect the layout according to the style.
-
- (defun ORDERED-NODES+PLACEMENTS-AT-LEVEL (view-members level ordering)
- (declare (list view-members) (integer level) (keyword :ordering))
- (ecase ordering
- ((:as-found :reduce-crossings)
- (nodes+placements-at-level view-members level))
- ((:reverse-as-found :reverse-reduce-crossings)
- (reverse (nodes+placements-at-level view-members level)))))
-
- (defun NODES+PLACEMENTS-AT-LEVEL (view-members level)
- (declare (list view-members) (integer level)
- (optimize (safety 1) (space 2) (speed 3)))
- (remove-if-not
- #'(lambda (node+placement)
- (declare (cons node+placement))
- (= level (node-placement-level (cdr node+placement))))
- view-members))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Drawing
-
- (defun DRAW-PARENT-TO-CHILD-LINK (pnode+placement cnode+placement layout-style)
- ;;
- ;; Sensitive to what the layout style is, whether the child is in a lower
- ;; or higher layer, and where they are positioned relative to each other.
- ;; Based on all this, tries to attach the links in nice looking locations
- ;; (i.e. the midpoint of the sides of the nodes facing each other, except
- ;; when the link is a backlink, which is visually indicated by not using
- ;; the normal attachment points). Big, ugly, brute-force approach.
- ;;
- (declare (cons pnode+placement cnode+placement) (keyword layout-style)
- (optimize (safety 0) (space 2) (speed 3)))
-
- (let ((parent-struct (sm:gets 'graph-node (car pnode+placement)))
- (child-struct (sm:gets 'graph-node (car cnode+placement)))
- (child-quadrant (node-placement-quadrant (cdr cnode+placement)))
-
- ;; Want to know relative position in cartesian coordinates regardless
- ;; of layout, so we know what sides are facing each other.
- (parent-v-<-child-v?
- (> (- (point-v (node-placement-position (cdr cnode+placement)))
- (point-v (node-placement-position (cdr pnode+placement))))
- *relative-v-position-threshold*))
- (parent-h-<-child-h?
- (> (- (point-h (node-placement-position (cdr cnode+placement)))
- (point-h (node-placement-position (cdr pnode+placement))))
- *relative-h-position-threshold*))
- (child-v-<-parent-v?
- (> (- (point-v (node-placement-position (cdr pnode+placement)))
- (point-v (node-placement-position (cdr cnode+placement))))
- *relative-v-position-threshold*))
- (child-h-<-parent-h?
- (> (- (point-h (node-placement-position (cdr pnode+placement)))
- (point-h (node-placement-position (cdr cnode+placement))))
- *relative-h-position-threshold*))
-
- ;; These will be computed
- (parent-attachment 0)
- (child-attachment 0))
-
- (declare (type graph-node parent-struct child-struct)
- (fixnum parent-attachment child-attachment))
-
- ;; Three cases for horizontal and vertical relative positioning: less,
- ;; equal (within an error tolerance), or greater. Gives 9 cases. Then
- ;; subdivide further by layout style.
- ;;
- (cond
-
- (parent-v-<-child-v?
- (cond
-
- (parent-h-<-child-h?
- ;;
- ;; Parent above and to left of child.
- ;;
- (ecase layout-style
- ((:vertical-tree :vertical-tree*)
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-bottom-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-top-center child-struct))))
- ((:horizontal-tree :horizontal-tree*)
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-right-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-left-center child-struct))))
- ((:radial :radial*)
- (ecase child-quadrant
- ((:upper)
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-right-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-left-center child-struct))))
- ((:lower :left :right)
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-bottom-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-top-center child-struct)))))))
- )
-
- (child-h-<-parent-h?
- ;;
- ;; Parent above and to right of child.
- ;;
- (ecase layout-style
- ((:vertical-tree :vertical-tree* :horizontal-tree :horizontal-tree*)
- ;; It's a backlink in the horizontal styles.
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-bottom-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-top-center child-struct))))
- ((:radial :radial*)
- (ecase child-quadrant
- ((:upper :left)
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-left-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-right-center child-struct))))
- ((:lower :right)
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-bottom-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-top-center child-struct)))))))
- )
-
- (T
- ;;
- ;; Parent centered over child.
- ;;
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-bottom-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-top-center child-struct)))
- )))
-
- (child-v-<-parent-v?
- (cond
-
- (parent-h-<-child-h?
- ;;
- ;; Parent below and to left of child.
- ;;
- (ecase layout-style
- ((:horizontal-tree :horizontal-tree* :vertical-tree :vertical-tree*)
- ;; Backlink for vertical styles.
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-right-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-left-center child-struct))))
- ((:radial :radial*)
- (ecase child-quadrant
- ((:upper :left)
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-top-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-bottom-center child-struct))))
- ((:lower :right)
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-right-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-left-center child-struct)))))))
- )
-
- (child-h-<-parent-h?
- ;;
- ;; Parent below and to right of child.
- ;;
- (ecase layout-style
- ((:vertical-tree :vertical-tree*)
- ;; Backlink.
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-left-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-right-center child-struct))))
- ((:horizontal-tree :horizontal-tree*)
- ;; Backlink.
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-top-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-bottom-center child-struct))))
- ((:radial :radial*)
- (case child-quadrant
- ((:upper :right)
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-top-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-bottom-center child-struct))))
- ((:lower :left)
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-left-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-right-center child-struct)))))))
- )
-
- (T
- ;;
- ;; Parent centered under child.
- ;;
- (ecase layout-style
- ((:vertical-tree :vertical-tree*)
- ;; Backlink.
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-right-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-right-center child-struct))))
- ((:horizontal-tree :horizontal-tree* :radial :radial*)
- ;; Backlink-ish for horizontal.
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-top-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-bottom-center child-struct)))))
- )))
-
- (T
- (cond
-
- (parent-h-<-child-h?
- ;;
- ;; Parent on same level and to left of child.
- ;;
- (ecase layout-style
- ((:vertical-tree :vertical-tree*)
- ;; Backlink-ish for vertical styles.
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-bottom-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-bottom-center child-struct))))
- ((:horizontal-tree :horizontal-tree* :radial :radial*)
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-right-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-left-center child-struct)))))
- )
-
- (child-h-<-parent-h?
- ;;
- ;; Parent on same level and to right of child.
- ;;
- (ecase layout-style
- ((:vertical-tree :vertical-tree*)
- ;; Backlink-ish for vertical styles.
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-bottom-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-bottom-center child-struct))))
- ((:horizontal-tree :horizontal-tree*)
- ;; Backlink.
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-top-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-top-center child-struct))))
- ((:radial :radial*)
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-left-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-right-center child-struct)))))
- )
-
- (T
- ;;
- ;; Nodes appear to be at same location! Use default linkage for style.
- ;;
- (ecase layout-style
- ((:vertical-tree :vertical-tree*)
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-bottom-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-top-center child-struct))))
- ((:horizontal-tree :horizontal-tree*)
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-right-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-left-center child-struct))))
- ((:radial :radial*)
- (setf parent-attachment
- (add-points (node-placement-position (cdr pnode+placement))
- (graph-node-top-center parent-struct)))
- (setf child-attachment
- (add-points (node-placement-position (cdr cnode+placement))
- (graph-node-top-center child-struct)))))
- ))))
-
- ;; If connector requested, make blob to mark head of arc.
- (if (graph-node-connector (sm:gets 'graph-node (car cnode+placement)))
- (paint-oval (add-points (make-point (- *connector-radius*)
- (- *connector-radius*))
- child-attachment)
- (add-points (make-point *connector-radius* *connector-radius*)
- child-attachment)))
-
- ;; Draw the line.
- (move-to parent-attachment)
- (line-to child-attachment)))
-
- (defun DRAW-GRAPH-NODE (node+placement node-font)
- (declare (cons node+placement) (list node-font)
- (optimize (safety 0) (space 2) (speed 3)))
- (let* ((node-struct (sm:gets 'graph-node (car node+placement)))
- (box-style (graph-node-box-style node-struct))
- (box-size (graph-node-box-size node-struct))
- (upper-left-corner-position
- (node-placement-position (cdr node+placement)))
- (lower-right-corner-position
- (add-points upper-left-corner-position box-size)))
- (declare (type graph-node node-struct) (keyword box-style)
- (fixnum box-size upper-left-corner-position lower-right-corner-position))
-
- ;; White out underlying stuff according to the box style; Draw box outline.
- (set-pen-pattern *white-pattern*)
- (case box-style
- ((:none :none-frame)
- (paint-rect upper-left-corner-position lower-right-corner-position)
- (pen-normal))
- ((:rect :frame)
- (paint-rect upper-left-corner-position lower-right-corner-position)
- (pen-normal)
- (frame-rect upper-left-corner-position lower-right-corner-position))
- ((:round-rect :round-frame)
- (paint-round-rect
- (point-v box-size) ; oval width
- (point-v box-size) ; oval height
- upper-left-corner-position lower-right-corner-position)
- (pen-normal)
- (frame-round-rect
- (point-v box-size)
- (point-v box-size)
- upper-left-corner-position lower-right-corner-position))
- ((:oval)
- (paint-oval upper-left-corner-position lower-right-corner-position)
- (pen-normal)
- (frame-oval upper-left-corner-position lower-right-corner-position)))
-
- ;; Write in label(s), with placement sensitive to size and style of box.
- (if (member box-style *framed-styles*)
- (do ((lptr (graph-node-label node-struct) (cdr lptr))
- (count 0 (1+ count))
- (line-height
- (multiple-value-bind (a d) (font-info node-font)
- (+ a d (if (< (second node-font) *small-font-threshold*)
- *small-font-box-height-padding*
- *large-font-box-height-padding*)))))
- ((null lptr))
- (declare (list lptr) (fixnum count))
- (move-to (add-points
- upper-left-corner-position ; reference point
- (make-point
- (ecase box-style ; horizontal indentation
- ((:frame :none-frame) *rect-text-h-indentation*)
- ((:round-frame) *round-frame-text-h-indentation*))
- (+ (if (< (second node-font) *small-font-threshold*)
- *small-font-v-text-indentation*
- *large-font-v-text-indentation*) ; vertical indentation
- (truncate (/ (float line-height) 2.0))
- (* count line-height)))))
- (map nil #'stream-tyo (car lptr)))
- (progn
- (move-to (add-points
- upper-left-corner-position ; reference point
- (make-point ; indentation
- (ecase box-style
- ((:none :rect) *rect-text-h-indentation*)
- ((:round-rect) *round-rect-text-h-indentation*)
- ((:oval) *oval-text-h-indentation*))
- (+ (if (< (second node-font) *small-font-threshold*)
- *small-font-v-text-indentation*
- *large-font-v-text-indentation*)
- (truncate (/ (float (point-v box-size)) 2.0))))))
- (map nil #'stream-tyo (graph-node-label node-struct))))))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; INTERACTIVE and I/O STUFF
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (defun GRAPH-VIEW-PARAMETER-DIALOGUE (label actual-roots graph-nodes
- &optional
- (default-style :horizontal-tree*)
- (default-ordering :reduce-crossings)
- (default-depth 2))
- "graph-view-parameter-dialogue [Function]
- <label> <actual-roots> <graph-nodes>
- &optional <default-style> <default-ordering> <default-depth>
- Uses a modal dialoge interaction to get and return as 4 multiple values
- the Roots, Style, Ordering, and Depth parameters of a graph view. The
- required arguments are <label> to identify the graph view; <actual-roots>,
- the true roots (sources) of the graph (the user may elect to display from
- other sources); and <graph-nodes> a list of nodes in the graph, from which
- the user may choose alternate roots."
- (check-type actual-roots list)
- (check-type graph-nodes list)
- (check-type default-style keyword)
- (check-type default-ordering keyword)
- (check-type default-depth fixnum)
-
- (let*
- (
- (instructions
- (oneof
- *static-text-dialog-item*
- :dialog-item-position (make-point 10 5)
- :dialog-item-font '("chicago" 12)
- :dialog-item-text
- (format nil "Choose Graph View Parameters for ~S" label)))
- (top-labels
- (oneof
- *static-text-dialog-item*
- :dialog-item-position (make-point 10 30)
- :dialog-item-font '("chicago" 12)
- :dialog-item-text
- "Graph Roots Graph Depth"))
- (roots-menu
- (oneof
- *sequence-dialog-item*
- :dialog-item-size (make-point 150 168)
- :dialog-item-position (make-point 10 50)
- :table-vscrollp t
- :table-hscrollp nil
- :visible-dimensions (make-point 1 6)
- :cell-size (make-point 150 16)
- :table-sequence (if actual-roots
- (cons '|Use Actual Roots| graph-nodes)
- graph-nodes)
- :sequence-order :vertical
- :selection-type :disjoint))
- (depth-menu
- (oneof
- *sequence-dialog-item*
- :dialog-item-size (make-point 150 168)
- :dialog-item-position (make-point 190 50)
- :table-vscrollp t
- :table-hscrollp nil
- :visible-dimensions (make-point 1 6)
- :cell-size (make-point 150 16)
- :table-sequence (cons default-depth '(0 1 2 3 4 5 6 7 8 9 10 20 30 40 50))
- :sequence-order :vertical
- :selection-type :single))
- (bottom-labels
- (oneof
- *static-text-dialog-item*
- :dialog-item-position (make-point 10 160)
- :dialog-item-font '("chicago" 12)
- :dialog-item-text
- "Layout Style Child Ordering"))
- (style-menu
- (oneof
- *sequence-dialog-item*
- :dialog-item-size (make-point 150 168)
- :dialog-item-position (make-point 10 180)
- :table-vscrollp t
- :table-hscrollp nil
- :visible-dimensions (make-point 1 6)
- :cell-size (make-point 150 16)
- ;; The slot type is specified with (member <object1> ... <objectN>)
- :table-sequence
- (cons default-style
- (remove default-style
- (cddr (assoc 'style (sm:slot-types 'graph-view)))))
- :sequence-order :vertical
- :selection-type :single))
- (order-menu
- (oneof
- *sequence-dialog-item*
- :dialog-item-size (make-point 150 168)
- :dialog-item-position (make-point 190 180)
- :table-vscrollp t
- :table-hscrollp nil
- :visible-dimensions (make-point 1 6)
- :cell-size (make-point 150 16)
- ;; The slot type is specified with (member <object1> ... <objectN>)
- :table-sequence
- (cons default-ordering
- (remove default-ordering
- (cddr (assoc 'ordering (sm:slot-types 'graph-view)))))
- :sequence-order :vertical
- :selection-type :single))
- (OK-button
- (oneof
- *button-dialog-item*
- :dialog-item-text " OK "
- :dialog-item-position (make-point 380 75)
- :dialog-item-action
- #'(lambda ()
- (let ((roots (ask roots-menu (selected-cells)))
- (style (ask style-menu (selected-cells)))
- (order (ask order-menu (selected-cells)))
- (depth (ask depth-menu (selected-cells))))
- (when (and roots style order depth)
- (return-from-modal-dialog
- (values
- (let ((chosen-roots
- (ask roots-menu (mapcar #'cell-contents roots))))
- (if (member '|Use Actual Roots| chosen-roots)
- (union actual-roots (delete '|Use Actual Roots| chosen-roots))
- chosen-roots))
- (ask style-menu (cell-contents (car style)))
- (ask order-menu (cell-contents (car order)))
- (ask depth-menu (cell-contents (car depth))))))
- (ed-beep)))
- :default-button t))
- (Cancel-button
- (oneof
- *button-dialog-item*
- :dialog-item-text "CANCEL"
- :dialog-item-position (make-point 370 105)
- :dialog-item-action
- #'(lambda () (return-from-modal-dialog :cancel))))
- (the-dialogue
- (oneof *dialog*
- :window-title (format nil "Graph View Parameters for ~A" label)
- :window-size (make-point 440 290)
- :window-position :centered
- :window-show t
- :window-type :double-edge-box
- :dialog-items
- (list instructions top-labels bottom-labels
- roots-menu style-menu order-menu depth-menu
- ok-button cancel-button))))
- (ask roots-menu (when (table-sequence) (cell-select (index-to-cell 0))))
- (ask style-menu (when (table-sequence) (cell-select (index-to-cell 0))))
- (ask order-menu (when (table-sequence) (cell-select (index-to-cell 0))))
- (ask depth-menu (when (table-sequence) (cell-select (index-to-cell 0))))
- (modal-dialog the-dialogue)))
-
- (defun SAVE-GRAPH-VIEW (graph-view path)
- "save-graph-view <graph-view> [Function]
- Writes the macro definitions of <graph-view> and all its member
- graph-nodes to a file specified by <path>."
- (check-type graph-view symbol)
- (assert (sm:gets 'graph-view graph-view) (graph-view)
- "[GRAPHER:SAVE-GRAPH-VIEW] Unknown Graph-View ~S" graph-view)
- (check-type path (or simple-string pathname))
- (let ((*print-pretty* nil) (*print-escape* t)
- (*print-circle* nil) (*print-case* :upcase) (*print-array* t)
- #+:ccl (*print-structure* t)
- (graph-view-struct (sm:gets 'graph-view graph-view)))
- (declare (type graph-view graph-view-struct))
- (with-open-file (stream path
- :direction :output
- :if-exists :supersede)
- (format stream ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Graph View ~S~%;;; Saved by SAVE-GRAPH-VIEW ~A~%;;; On the ~A"
- graph-view
- (multiple-value-bind
- (second minute hour date month year)
- (get-decoded-time)
- (declare (integer second minute hour date month year))
- (format nil "~2,'0D-~A-~2,'0D ~2,'0D:~2,'0D:~2,'0D"
- date
- (case month
- ((1) "Jan") ((2) "Feb") ((3) "Mar") ((4) "Apr")
- ((5) "May") ((6) "Jun") ((7) "Jul") ((8) "Aug")
- ((9) "Sep") ((10) "Oct") ((11) "Nov") ((12) "Dec"))
- (- year 1900)
- hour minute second))
- (machine-type))
- (format stream "~%(in-package ~S)~%~%" (package-name *package*))
- (sm:prints 'graph-view graph-view
- :style :macro :stream stream
- :omit '(mouse-methods))
- ;; Will have to do with the most basic mouse methods when restored.
- (format stream "~%~%(setf ~S~% ~S)~%"
- `(graph-view-mouse-methods (sm:gets 'graph-view ',graph-view))
- (cdr (assoc 'mouse-methods (sm:slot-defaults 'graph-view))))
- (dolist (gn+p (graph-view-members graph-view-struct))
- (format stream "~%")
- (sm:prints 'graph-node (car gn+p) :style :macro :stream stream))
- (format stream "~&~%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; EOF"))
- path))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Graphing Arbitrary SM Objects
-
- (defparameter *SM-MOUSE-METHODS*
- (append
- (list
- (cons
- "Make this Node the Root"
- (compile
- nil
- '(lambda (gw gv gn)
- (ccl:ask gw
- (let* ((gv-struct (sm:gets 'graph-view gv))
- (new-gv
- (sm-type->graph-view
- (graph-view-info-image :sm-type gv)
- (graph-view-info-image :access-slot gv)
- (list (graph-node-object
- (sm:gets 'graph-node gn)))
- (graph-view-info-image :child-access-p gv)
- (graph-view-style gv-struct)
- (graph-view-ordering gv-struct)
- (graph-view-depth-bound gv-struct)
- gv))) ; parent view
- (set-graph-view new-gv)
- (ccl:set-window-title
- (sm:prints 'graph-view new-gv
- :style :name :stream nil))
- (ccl:window-select)
- (view-draw-contents))))))
-
- (cons
- "Backup Once to Parent View"
- (compile
- nil
- '(lambda (gw gv gn)
- (declare (ignore gn))
- (ccl:ask gw
- (let ((parent-view
- (graph-view-info-image :parent-view gv)))
- (if parent-view
- (if (sm:gets 'graph-view parent-view)
- (progn
- (set-graph-view parent-view :layout nil) ; already laid out
- (ccl:set-window-title
- (sm:prints 'graph-view parent-view
- :style :name :stream nil))
- (ccl:window-select)
- (view-draw-contents)
- (unless (windows-using-graph-view gv)
- (eval-enqueue `(dispose-graph-view ',gv))))
- (progn
- (ccl:ed-beep)
- (setf (graph-view-info-image :parent-view gv) nil)
- (let ((wind:*dialogue-position*
- (upper-left-popup-position gw)))
- (wind:message-dialogue
- "The parent view appears to have been destroyed."))
- ;; The graph-view of gw was set to nil since we
- ;; thought gv was to be replaced ... restore it.
- (set-graph-view gv :layout nil)
- (view-draw-contents)))
- (progn (ccl:ed-beep)
- (let ((wind:*dialogue-position*
- (upper-left-popup-position gw)))
- (wind:message-dialogue
- "This graph view has no parent view."))
- (set-graph-view gv :layout nil)
- (view-draw-contents)))))))) ; see comment above
-
- (cons
- "New Window with this Node as Root"
- (compile
- nil
- '(lambda (gw gv gn)
- (let* ((type (graph-view-info-image :sm-type gv))
- (gv-struct (sm:gets 'graph-view gv))
- (roots
- (list (graph-node-object (sm:gets 'graph-node gn))))
- (style (graph-view-style gv-struct))
- (ordering (graph-view-ordering gv-struct))
- (depth-bound (graph-view-depth-bound gv-struct)))
- (multiple-value-setq
- (roots style ordering depth-bound)
- (graph-view-parameter-dialogue
- type roots nil style ordering depth-bound))
- (ccl:oneof
- *graph-window*
- :graph-view
- (sm-type->graph-view type
- (graph-view-info-image :access-slot gv)
- roots
- (graph-view-info-image :child-access-p gv)
- style ordering depth-bound gv))))))
-
- (cons
- "Backup All the Way to Original View"
- (compile
- nil
- '(lambda (gw gv gn)
- (declare (ignore gn))
- (ccl:ask gw
- (let ((garbage-views nil) (original-view nil))
- ;; Search up to find original view; also recording the views
- ;; to be disposed of along the way.
- (do* ((parent-view
- (graph-view-info-image :parent-view gv)
- (graph-view-info-image :parent-view current-view))
- (current-view gv))
- ;; Invariant here: parent-view is parent of current-view,
- ;; so when parent-view nil, current-view is the root.
- ((null parent-view) (setq original-view current-view))
- (if (sm:gets 'graph-view parent-view)
- (progn
- (push current-view garbage-views)
- (setq current-view parent-view))
- (progn
- (ccl:ed-beep)
- (setf (graph-view-info-image :parent-view current-view) nil)
- (let ((wind:*dialogue-position*
- (upper-left-popup-position gw)))
- (wind:message-dialogue
- "The parent of view ~A appears to have been destroyed."
- current-view))
- (setq parent-view nil)))) ; to exit
- (set-graph-view original-view :layout nil) ; already laid out
- (ccl:set-window-title
- (sm:prints 'graph-view original-view :style :name :stream nil))
- (ccl:window-select)
- (view-draw-contents)
- (dolist (ggv garbage-views)
- (unless (windows-using-graph-view ggv)
- (eval-enqueue `(dispose-graph-view ',ggv)))))))))
-
- (cons
- "Edit Associated Object"
- (compile
- nil
- '(lambda (gw gv gn)
- (declare (ignore gw))
- (sm:edits (graph-view-info-image :sm-type gv)
- (graph-node-object (sm:gets 'graph-node gn))))))
-
- (cons
- "Update Graph View for Changes"
- (compile
- nil
- '(lambda (gw gv gn)
- (declare (ignore gn))
- (let* ((gv-struct (sm:gets 'graph-view gv))
- (type (graph-view-info-image :sm-type gv))
- (access-slot (graph-view-info-image :access-slot gv))
- (roots (graph-view-info-image :original-roots gv))
- (child-access-p (graph-view-info-image :child-access-p gv))
- (style (graph-view-style gv-struct))
- (ordering (graph-view-ordering gv-struct))
- (depth-bound (graph-view-depth-bound gv-struct))
- (parent-view (graph-view-info-image :parent-view gv))
- (new-view nil))
- (multiple-value-setq
- (roots style ordering depth-bound)
- (graph-view-parameter-dialogue type roots nil style ordering depth-bound))
- (ccl:ask gw
- (set-graph-view nil)
- (setq new-view
- (sm-type->graph-view
- type access-slot roots child-access-p
- style ordering depth-bound parent-view))
- (unless (windows-using-graph-view gv)
- (eval-enqueue `(dispose-graph-view ',gv)))
- (set-graph-view new-view)
- (ccl:set-window-title
- (sm:prints 'graph-view new-view :style :name :stream nil))
- (ccl:window-select)
- (view-draw-contents))))))
-
- (cons
- "Inspect Structure"
- (compile
- nil
- '(lambda (gw gv gn)
- (inspect
- (sm:gets (graph-view-info-image :sm-type gv)
- (graph-node-object (sm:gets 'graph-node gn)))))))
-
- )
- ;; Note that SM stores unevaluated expressions producing defaults.
- (eval
- (cdr (assoc 'mouse-methods
- (sm:slot-defaults 'graph-view))))))
-
- (defun GRAPH-SM-OBJECTS (type access-slot roots
- &optional (child-access-p t)
- (style :horizontal-tree*)
- (ordering :reduce-crossings)
- (depth-bound 2)
- (label-function #'symbol-name))
- "graph-sm-objects <type> <access-slot> <roots>
- &optional <child-access-p> <style> <ordering> <depth-bound>
- <label-function>
- <type> is an SM type; <roots> a list of instance names; <access-slot> the
- name of an SM slot; <child-access-p> (default t) is T if <access-slot> lists
- child instances, and NIL if it lists parent instances (in which case computing
- graph members is slower); <depth-bound> defaults 2; <style> defaults
- :horizontal-tree*, and <ordering> defaults :reduce-crossings. <Label-function>
- defaults to SYMBOL-NAME, and should be a function of one argument mapping names
- of instances of <type> to string labels."
- (check-type type symbol)
- (check-type roots list)
- (check-type access-slot symbol)
- (check-type depth-bound fixnum)
- (check-type style keyword)
- (check-type ordering keyword)
- (check-type label-function function)
- (assert (member type (sm:structure-types)) (type) "Unknown type ~S" type)
- (dolist (i roots)
- (assert (sm:gets type i) (roots) "Unknown instance ~S" i))
- (assert (assoc access-slot (sm:slot-access type))
- (access-slot)
- "Slot ~S is not defined for type ~S" access-slot type)
-
- ;; Get desired parameters.
- (multiple-value-setq
- (roots style ordering depth-bound)
- (graph-view-parameter-dialogue
- type roots (sm:instances type) style ordering depth-bound))
-
- ;; Make graph view and put up in window.
- (oneof *graph-window*
- :graph-view
- (sm-type->graph-view
- type access-slot roots child-access-p style ordering depth-bound
- nil label-function)))
-
- (defun SM-TYPE->GRAPH-VIEW (type access-slot roots child-access-p
- &optional (style :horizontal-tree*)
- (ordering :reduce-crossings)
- (depth-bound 2) parent-view
- (label-function #'symbol-name))
- "sm-type->graph-view <type> <access-slot> <roots> <child-access-p>
- &optional <style> <ordering> <depth-bound> <parent-view>
- <label-function>
- Returns a graph-view of instances of the SM type with the indicated parameters.
- The optional <parent-view>, if given, should be a graph view, presumably one
- containing as a node the root of the current view. <Label-function> is given
- a function of one argument which maps names of the instances of <type> to the
- label (string) to put on the graph node. This is an 'internal' function which
- does NO ARGUMENT CHECKING. "
- (declare (symbol type access-slot) (list roots) (keyword style ordering)
- (fixnum depth-bound) (function label-function)
- (optimize (safety 1) (space 2) (speed 3)))
-
- (create-graph-view
- ;; Make unique name. If root is unique, graph view is probably "about" that
- ;; root, so include it in name. Othewise, just use type to generate name.
- (utils:unique-symbol
- (if (= (length roots) 1)
- (format nil "~A ~A " type (first roots))
- (format nil "~A " type)))
- ;; Roots argument to this function is list of SM instances. Actual graph view
- ;; has graph-node instances as roots, returned by the call below.
- (make-view-nodes-returning-roots
- type access-slot roots child-access-p depth-bound parent-view label-function)
- depth-bound style ordering
- '("monaco" 9) '("chicago" 9) 10
- ;; Record this in INFO for use of mouse method.
- `((:sm-type . ,type) (:parent-view . ,parent-view)
- (:access-slot . ,access-slot) (:child-access-p . ,child-access-p)
- (:original-roots . ,roots))
- *sm-mouse-methods*))
-
- (defun MAKE-VIEW-NODES-RETURNING-ROOTS (type access-slot roots
- child-access-p depth-bound parent-view
- label-function)
- (declare (symbol type access-slot) (list roots) (fixnum depth-bound)
- (function label-function) (optimize (safety 1) (space 2) (speed 3)))
-
- ;; Complication: graph node names will not be the same as the instance names.
- ;; Mapping of instance names to graph nodes must be done before filling in
- ;; child slots, so two passes needed. Within each pass, some code is
- ;; duplicated based on whether access-slot is child-access-p.
- (let ((instance->graph-node nil)
- (access-function (cdr (assoc access-slot (sm:slot-access type)))))
- (declare (list instance->graph-node) (function access-function))
-
- ;; Iterate over successive frontiers of new reachable instances, making nodes.
- (do ((frontier roots)
- (new-frontier nil nil)
- (depth 0 (1+ depth)))
- ((or (null frontier) (> depth depth-bound)))
- (declare (list frontier))
-
- ;; Create a graph node for each instance, and expand the frontier.
- (dolist (fn frontier)
- (declare (symbol fn))
- (push (cons fn
- (create-graph-node (gensym "GRAPH-NODE-")
- (funcall label-function fn)
- nil ; children (computed below)
- :rect ; box-style default (may change)
- T ; connector
- fn)) ; associated object
- instance->graph-node)
-
- ;; Put on new frontier those children we have not seen (unless past bound).
- (unless (= depth depth-bound)
- (if child-access-p
- (dolist (child (funcall access-function (sm:gets type fn)))
- (declare (symbol child))
- (if (not (assoc child instance->graph-node))
- (push child new-frontier)))
- (dolist (i (sm:instances type)) ; searching for inverse links
- (declare (symbol i))
- (if (and (member fn (funcall access-function (sm:gets type i)))
- (not (assoc i instance->graph-node)))
- (push i new-frontier))))))
- (setf frontier new-frontier))
-
- ;; Second Pass: Now that all members are known, put in child info.
- ;; Box style becomes round-rect if child seen, to indicate subtree exists.
- (dolist (i+gn instance->graph-node)
- (declare (cons i+gn))
- (let ((children
- (if child-access-p
- (funcall access-function (sm:gets type (car i+gn)))
- (mapcan #'(lambda (i) ; searching for children again
- (declare (symbol i))
- (if (member (car i+gn)
- (funcall access-function (sm:gets type i)))
- (list i)))
- (sm:instances type)))))
- (declare (list children))
- (when children
- (setf (graph-node-box-style (sm:gets 'graph-node (cdr i+gn))) :round-rect)
- (dolist (child children)
- (declare (symbol child))
- ;; Filtering out children not in the graph view due to cutoff.
- (let ((child-graph-node (cdr (assoc child instance->graph-node))))
- (when child-graph-node
- (push child-graph-node
- (graph-node-children (sm:gets 'graph-node (cdr i+gn))))))))))
-
- ;; Return graph view roots (the images of root instances).
- (mapcar #'(lambda (ri)
- (declare (symbol ri))
- (let ((root (cdr (assoc ri instance->graph-node))))
- (unless parent-view ; top level view roots are ovals
- (setf (graph-node-box-style (sm:gets 'graph-node root)) :oval))
- root))
- roots)))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; CREATE MENU
-
- (defparameter *GRAPHER-MENU*
- (let*
- (
- (line-item
- (oneof *menu-item*
- :menu-item-title "-"))
-
- (new-window-item
- (oneof *menu-item*
- :menu-item-title "New Graph Window ..."
- :menu-item-action
- #'(lambda (&aux gw)
- (setf gw
- (oneof
- *graph-window*
- :graph-view
- (wind:menu-dialogue
- (cons NIL (sm:instances 'graph-view))
- "Which graph view do you want in the Graph Window?")))
- (ask gw (window-select))))) ; will layout and draw.
-
- (set-view-item
- (oneof *menu-item*
- :menu-item-title "Set Graph View ..."
- :menu-item-action
- #'(lambda ()
- (let* ((graph-window-names
- (mapcar #'(lambda (gw) (ask gw (window-title)))
- (windows *graph-window*)))
- (graph-window
- (utils:image
- (wind:menu-dialogue
- graph-window-names
- "Which window do you want to give a new Graph View?")
- (pairlis graph-window-names (windows *graph-window*))))
- (graph-view
- (if graph-window
- (wind:menu-dialogue
- (cons NIL (sm:instances 'graph-view))
- "Which graph view do you want ~A to have?" graph-window))))
- ;; Give the window below the menu a chance to redraw before we
- ;; start messing with the layout. (Had a problem with :radial).
- (sleep .5)
- (ask graph-window
- (set-graph-view graph-view :layout t)
- (view-draw-contents))))))
-
- (recompute-item
- (oneof *menu-item*
- :menu-item-title "ReCompute Layout..."
- :menu-item-action
- #'(lambda ()
- (let* ((graph-window-names
- (mapcar #'(lambda (gw) (ask gw (window-title)))
- (windows *graph-window*)))
- (chosen-windows
- (wind:multiple-menu-dialogue
- graph-window-names
- "(Re)Compute layout of which graph view windows?"))
- (window->object
- (pairlis graph-window-names (windows *graph-window*))))
- ;; Give the window below the menu a chance to redraw before we
- ;; start messing with the layout. (Had a problem with :radial).
- (sleep .5)
- (dolist (graph-window chosen-windows)
- (ask (utils:image graph-window window->object)
- (layout-graph-view)
- (view-draw-contents)))))))
-
- (change-view-parameters-item
- (oneof *menu-item*
- :menu-item-title "Change View Parameters..."
- :menu-item-action
- #'(lambda ()
- (let* ((graph-view
- (wind:menu-dialogue
- (sm:instances 'graph-view)
- "Which graph view do you wish to change the parameters of?"))
- (graph-view-itself (sm:gets 'graph-view graph-view)))
- (multiple-value-bind
- (roots style ordering depth-bound)
- (graph-view-parameter-dialogue
- graph-view
- (graph-view-roots graph-view-itself)
- (mapcar #'car (graph-view-members graph-view-itself)))
- ;; Give the window below the menu a chance to redraw before we
- ;; start messing with the layout. (Had a problem with :radial).
- (sleep .5)
- (setf (graph-view-roots graph-view-itself) roots)
- (setf (graph-view-style graph-view-itself) style)
- (setf (graph-view-ordering graph-view-itself) ordering)
- (setf (graph-view-depth-bound graph-view-itself) depth-bound))))))
-
- (list-windows-using-item
- (oneof *menu-item*
- :menu-item-title "Windows Using View..."
- :menu-item-action
- #'(lambda ()
- (let* ((graph-view
- (wind:menu-dialogue
- (sm:instances 'graph-view)
- "For which graph view do you want a list of windows using it?"))
- (active-windows (windows-using-graph-view graph-view)))
- (if active-windows
- (wind:menu-dialogue
- (mapcar #'(lambda (w) (ask w (window-title))) active-windows)
- "These windows are still using ~S"
- graph-view)
- (wind:message-dialogue "No windows are using ~S" graph-view))))))
-
- (dispose-graph-view-item
- (oneof *menu-item*
- :menu-item-title "Dispose Graph Views..."
- :menu-item-action
- #'(lambda ()
- (dolist (graph-view
- (wind:multiple-menu-dialogue
- (sm:instances 'graph-view)
- "Dispose of which graph views?"))
- ;; Give the window below the menu a chance to redraw before we
- ;; start messing with the layout. (Had a problem with :radial).
- (sleep .5)
- (unless (windows-using-graph-view graph-view)
- (eval-enqueue `(dispose-graph-view ',graph-view)))))))
-
- (save-item
- (oneof *menu-item*
- :menu-item-title "Save Graph View ..."
- :menu-item-action
- #'(lambda ()
- (let* ((gv (wind:menu-dialogue
- (sm:instances 'graph-view)
- "Which graph view do you want to save?~%(MOUSE-METHODS are NOT saved.)"))
- (file-path
- (pathname
- (choose-new-file-dialog
- :prompt
- (format nil "Save ~A to ..." gv))))
- (backup-path
- (make-pathname
- :host (pathname-host file-path)
- :device (pathname-device file-path)
- :directory (pathname-directory file-path)
- :name (pathname-name file-path)
- :type "bak")))
- (if (probe-file file-path)
- (progn
- (if (probe-file backup-path)
- (delete-file backup-path))
- (rename-file file-path backup-path)
- (format T "~&;~A backed up to ~A"
- (namestring file-path)
- (namestring backup-path))))
- (setf *default-instance-file-path*
- (directory-namestring file-path))
- (save-graph-view gv file-path)
- (format T "~&;Graph View ~A saved to ~S"
- gv
- (namestring file-path))))))
-
- (sm-graph-item
- (oneof *menu-item*
- :menu-item-title "Graph SM Objects ..."
- :menu-item-action
- #'(lambda ()
- (let* ((type
- (wind:menu-dialogue (sm:structure-types)
- "Graph instances of which type?"))
- (slot
- (or (sm:type-info type :graph-view-child-slot)
- (sm:type-info type :graph-view-parent-slot)
- (wind:menu-dialogue
- (mapcar #'car (sm:slot-access type))
- "Which slot of ~A holds the child or parent relation?"
- type)))
- (child-p
- (cond ((sm:type-info type :graph-view-child-slot) T)
- ((sm:type-info type :graph-view-parent-slot) nil)
- (T (wind:y-or-n-dialogue "Is this a child relation slot?")))))
- (if (sm:instances type)
- (graph-sm-objects
- type slot (sm:type-info type :graph-view-roots) child-p)
- (wind:message-dialogue
- "There are no instances of ~S to graph." type))))))
-
- (deinstall-item
- (oneof *menu-item*
- :menu-item-title "Hide This Menu"
- :menu-item-action
- '(ask *grapher-menu* (menu-deinstall))))
-
- (grapher-menu (oneof *menu*
- :menu-title "Grapher"
- :menu-items (list new-window-item
- recompute-item
- set-view-item
- line-item
- change-view-parameters-item
- list-windows-using-item
- save-item
- dispose-graph-view-item
- line-item
- sm-graph-item
- line-item
- deinstall-item))))
- (ask grapher-menu (menu-install))
- (ask line-item (menu-item-disable))
- ;; Menu-dispose dumped from version 1.3.1?
- (if (and (boundp '*grapher-menu*)
- (typep *grapher-menu* *menu*))
- (ask *grapher-menu* (menu-deinstall)))
- grapher-menu))
-
- (ask *tools-menu*
- (add-menu-items
- (oneof *menu-item*
- :menu-item-title "Restore Grapher Menu"
- :menu-item-action
- #'(lambda ()
- (ask *grapher-menu*
- (unless (menu-installed-p) (menu-install)))))))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- (provide :grapher)
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; EOF